package cn.hebutrun.oulabot.common.aop;

import cn.hebutrun.oulabot.common.Constants;
import cn.hebutrun.oulabot.common.util.IDUtil;
import cn.hebutrun.oulabot.common.util.IPUtil;
import cn.hebutrun.oulabot.common.util.LogUtil;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 日志打印切面
 *
 * @author zhaolong
 */
@Aspect
@Component
public class LogAop implements HandlerInterceptor {

  /** 本线程存储计时器 */
  private static final ThreadLocal<StopWatch> TIME = new ThreadLocal<>();

  /** 本线程存储日志信息 */
  private static final ThreadLocal<StringBuilder> LOG_INFO = new ThreadLocal<>();

  /**
   * 方法执行前、后
   *
   * @param point 切入信息
   */
  @Before("execution(public * cn..controller..*.*(..))")
  public void prepareLog(JoinPoint point) {
    // 取请求对象
    ServletRequestAttributes attributes =
        (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

    // 准备打印
    StringBuilder sb = new StringBuilder();
    sb.append("\n【请求类名】:").append(point.getSignature().getDeclaringTypeName());
    sb.append("\n【请求方法名】:").append(point.getSignature().getName());
    sb.append("\n【请求实体】:").append(JSONUtil.toJsonStr(point.getArgs()));
    request.setAttribute(Constants.LOG_ATTRIBUTE_NAME, sb);
  }

  /**
   * 在请求之前需要处理的内容
   *
   * @param request 请求对象
   * @param response 返回对象
   * @param handler handler对象
   * @return true or false
   */
  @Override
  public boolean preHandle(
      @NonNull HttpServletRequest request,
      @NonNull HttpServletResponse response,
      @NonNull Object handler) {
    // PreFlight请求，忽略本拦截器
    if (CorsUtils.isPreFlightRequest(request)) {
      return true;
    }
    // 开始计时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 计时器放在本线程中
    TIME.set(stopWatch);

    // 获取请求参数
    Map<String, String[]> parameterMap = request.getParameterMap();
    // 获取请求头部
    Enumeration<String> enumeration = request.getHeaderNames();
    Map<String, String> headerMap = new HashMap<>(16);
    Optional.ofNullable(enumeration).ifPresent(x -> {
      while (x.hasMoreElements()) {
        String key = x.nextElement();
        headerMap.put(key, request.getHeader(key));
      }
    });
    // 生成请求id
    String requestId = IDUtil.getRandomUUID();
    // 请求id放在request的域中
    request.setAttribute(Constants.REQUEST_ID_KEY, requestId);
    // 请求id放在log上下文中
    MDC.put(Constants.REQUEST_ID_KEY, requestId);
    // 准备日志信息
    StringBuilder sb = new StringBuilder();
    sb.append("\n【请求 ID】:").append(requestId);
    sb.append("\n【请求 URL】:").append(request.getRequestURL());
    sb.append("\n【请求 IP】:").append(IPUtil.getIP(request));
    sb.append("\n【请求类型】:").append(request.getMethod());
    sb.append("\n【请求Header】:").append(JSONUtil.toJsonStr(headerMap));
    sb.append("\n【请求参数】:").append(JSONUtil.toJsonStr(parameterMap));
    // 日志信息存放在本线程中
    LOG_INFO.set(sb);
    return true;
  }

  /**
   * 在请求全部完成之后
   *
   * @param request 请求
   * @param response 响应
   * @param handler handler
   * @param ex 异常
   */
  @Override
  public void afterCompletion(
      @NonNull HttpServletRequest request,
      @NonNull HttpServletResponse response,
      @NonNull Object handler,
      Exception ex) {
    if (!CorsUtils.isPreFlightRequest(request)) {
      this.printLog(request);
    }
  }

  /** 打印日志 */
  private void printLog(HttpServletRequest request) {
    // 取出计时器
    StopWatch stopWatch = TIME.get();
    // 计时器停止
    stopWatch.stop();
    // 取计时器耗时
    long time = stopWatch.getTotalTimeMillis();

    // 取出日志信息
    StringBuilder localLog = LOG_INFO.get();

    // 日志拼接接口耗时
    localLog.append("\n【接口耗时】:").append(time).append("ms");

    // 从request取aop打印的log信息
    Optional.ofNullable((StringBuilder) request.getAttribute(Constants.LOG_ATTRIBUTE_NAME))
        .ifPresent(localLog::append);
    // 打印请求日志
    LogUtil.info(localLog.toString());
    this.clear();
  }

  /** 清除变量 */
  private void clear() {
    // 计时器线程清除
    TIME.remove();
    // 日志信息线程清除
    LOG_INFO.remove();
    // 清除log上下文信息
    MDC.clear();
  }
}
